package com.dcache;

import com.dcache.cache.CacheMgr;
import com.dcache.cache.Keys;
import com.dcache.pojo.*;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.shared.Application;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.Date;
import java.util.List;

@SpringBootApplication
@Controller
@EnableEurekaClient
@Slf4j
public class DCacheApplication {

    @LoadBalanced
    @Bean
    RestTemplate loadBalanced() {
        return new RestTemplate();
    }

    @Primary
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    @LoadBalanced
    private RestTemplate loadBalanced;

	@Autowired
	LoadBalancerClient loadBalancer;

    @Autowired
    private EurekaClient discoveryClient;

	@Autowired
	CacheMgr cacheMgr;

	@Autowired
	Keys keys;

    @Value("${cache.copies}")
    private int copies;

    @Value("${cache.app-name}")
    private String cacheAppName;

	@RequestMapping("/")
	public String home() {
		return "index";
	}

	private CacheServiceInfo dCacheSave(String key, UserInfo userInfo) {

        CacheServiceInfo cacheServiceInfo = new CacheServiceInfo();
        cacheServiceInfo.setUserInfo(userInfo);
        cacheServiceInfo.setSuccess(true);

	    for (int i = 0; i < copies; i ++) {
            ServiceInstance serviceInstance = loadBalancer.choose(cacheAppName);
            URI storesUri = URI.create(String.format("http://%s:%s/set/%s", serviceInstance.getHost(), serviceInstance.getPort(), key));

            ResponseEntity<RespBody> responseEntity = restTemplate.postForEntity(storesUri, userInfo, RespBody.class);
            RespBody respBody = responseEntity.getBody();
            if(respBody.isSuccess()) {
                NodeInfo nodeInfo = NodeInfo.builder().ip(serviceInstance.getHost()).port(serviceInstance.getPort()).build();
                cacheServiceInfo.getNodeInfos().add(nodeInfo);
            } else {
                cacheServiceInfo.setSuccess(false);
            }
        }

        return cacheServiceInfo;

    }

	@RequestMapping(value = "/api/create", method = RequestMethod.POST)
	@ResponseBody
	public Object create(UserInfo userInfo) {
		String key = keys.generate();
		userInfo.setId(key);
		userInfo.setCreateDate(new Date());

		log.debug("[ui] create UserInfo: {}", userInfo);

        CacheServiceInfo cacheServiceInfo = this.dCacheSave(key, userInfo);

        cacheMgr.set(key, cacheServiceInfo);

		log.debug("[ui] CacheServiceInfo: {}", cacheServiceInfo);

		return userInfo;
	}

	private boolean dCacheUpdate(String key, UserInfo userInfo) {
        CacheServiceInfo cacheServiceInfo = cacheMgr.get(key);
        if(cacheServiceInfo == null) {
            return false;
        }
        boolean isAllSuccess = true;
        List<NodeInfo> nodeInfos = cacheServiceInfo.getNodeInfos();
        for(NodeInfo nodeInfo : nodeInfos) {
            ResponseEntity<RespBody> responseEntity = restTemplate.postForEntity("http://{ip}:{port}/update/{key}", userInfo, RespBody.class, nodeInfo.getIp(),nodeInfo.getPort(), key);
            RespBody respBody = responseEntity.getBody();
            if(!respBody.isSuccess()) {
                isAllSuccess = false;
            }
        }

        if(isAllSuccess) {
            UserInfo userInfo1 = cacheMgr.get(key).getUserInfo();
            userInfo1.setUpdateDate(userInfo.getUpdateDate());
        }

        return isAllSuccess;
    }

	@RequestMapping(value = "/api/update/{id}", method = RequestMethod.POST)
	@ResponseBody
	public Object update(@PathVariable("id") String id) {

        UserInfo userInfo = new UserInfo();
        userInfo.setUpdateDate(new Date());

        return this.dCacheUpdate(id, userInfo);
	}

    private boolean dCacheDelete(String key) {
        CacheServiceInfo cacheServiceInfo = cacheMgr.get(key);
        if(cacheServiceInfo == null) {
            return false;
        }
        boolean isAllSuccess = true;
        List<NodeInfo> nodeInfos = cacheServiceInfo.getNodeInfos();
        for(NodeInfo nodeInfo : nodeInfos) {
            ResponseEntity<RespBody> responseEntity = restTemplate.postForEntity("http://{ip}:{port}/delete/{key}", null, RespBody.class, nodeInfo.getIp(),nodeInfo.getPort(), key);
            RespBody respBody = responseEntity.getBody();
            if(!respBody.isSuccess()) {
                isAllSuccess = false;
            }
        }

        if(isAllSuccess) {
            cacheMgr.remove(key);
        }

        return isAllSuccess;
    }

	@RequestMapping(value = "/api/delete/{id}", method = RequestMethod.POST)
	@ResponseBody
	public Object delete(@PathVariable("id") String id) {
        return this.dCacheDelete(id);
	}

	@RequestMapping(value = "/api/list", method = RequestMethod.GET)
	@ResponseBody
	public Object list() {
        return cacheMgr.content();
	}

	@RequestMapping(value = "/api/get/{id}", method = RequestMethod.GET)
	@ResponseBody
	public Object get(@PathVariable("id") String id) {
        NodeInfo nodeInfo = cacheMgr.randomNode(id);
        if(nodeInfo != null) {
            ResponseEntity<RespBody> responseEntity = restTemplate.postForEntity("http://{ip}:{port}/get/{key}", null, RespBody.class, nodeInfo.getIp(),nodeInfo.getPort(), id);
            RespBody respBody = responseEntity.getBody();
            if(respBody.isSuccess()) {
                return new Object[]{respBody.getUserInfo(), nodeInfo};
            }
        }
        return null;
	}

	@RequestMapping(value = "/api/dash", method = RequestMethod.GET)
	@ResponseBody
	public Object dash() {
        Application application = discoveryClient.getApplication(cacheAppName);
        List<InstanceInfo> cacheClients = application.getInstancesAsIsFromEureka();
        List<InstanceInfo> cacheRegistries = discoveryClient.getApplication("cache-registry").getInstancesAsIsFromEureka();
        List<InstanceInfo> cacheUis = discoveryClient.getApplication("cache-ui").getInstancesAsIsFromEureka();
        return DashInfo.builder().cacheClients(cacheClients).cacheRegistries(cacheRegistries).cacheUis(cacheUis).build();
	}

	public static void main(String[] args) {
		SpringApplication.run(DCacheApplication.class, args);
	}
}
